/*- * See the file LICENSE for redistribution information. * * Copyright (c) 2002-2006 * Sleepycat Software. All rights reserved. * * $Id: DbTree.java,v 1.1 2006/05/06 09:00:25 ckaestne Exp $ */ package com.sleepycat.je.dbi; import java.io.PrintStream; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.Map; import com.sleepycat.je.Database; import com.sleepycat.je.DatabaseConfig; import com.sleepycat.je.DatabaseEntry; import com.sleepycat.je.DatabaseException; import com.sleepycat.je.DatabaseNotFoundException; import com.sleepycat.je.DeadlockException; import com.sleepycat.je.OperationStatus; import com.sleepycat.je.TransactionConfig; import com.sleepycat.je.VerifyConfig; import com.sleepycat.je.dbi.CursorImpl.SearchMode; import com.sleepycat.je.log.LogEntryType; import com.sleepycat.je.log.LogException; import com.sleepycat.je.log.LogReadable; import com.sleepycat.je.log.LogUtils; import com.sleepycat.je.log.LoggableObject; import com.sleepycat.je.tree.ChildReference; import com.sleepycat.je.tree.IN; import com.sleepycat.je.tree.LN; import com.sleepycat.je.tree.MapLN; import com.sleepycat.je.tree.NameLN; import com.sleepycat.je.tree.Tree; import com.sleepycat.je.tree.TreeUtils; import com.sleepycat.je.tree.WithRootLatched; import com.sleepycat.je.txn.AutoTxn; import com.sleepycat.je.txn.BasicLocker; import com.sleepycat.je.txn.LockType; import com.sleepycat.je.txn.Locker; /** * Represents the DatabaseImpl Naming Tree. */ public class DbTree implements LoggableObject, LogReadable { /* The id->DatabaseImpl tree is always id 0 */ public static final DatabaseId ID_DB_ID = new DatabaseId(0); /* The name->id tree is always id 1 */ public static final DatabaseId NAME_DB_ID = new DatabaseId(1); /* Names of the mapping database. */ public static final String ID_DB_NAME = "_jeIdMap"; public static final String NAME_DB_NAME = "_jeNameMap"; public static final String UTILIZATION_DB_NAME = "_jeUtilization"; /* Reserved database names. */ private static final String[] RESERVED_DB_NAMES = { ID_DB_NAME, NAME_DB_NAME, UTILIZATION_DB_NAME, }; /* Database id counter, must be accessed w/synchronization. */ private int lastAllocatedDbId; private DatabaseImpl idDatabase; // map db ids -> databases private DatabaseImpl nameDatabase; // map names -> dbIds private EnvironmentImpl envImpl; /** * Create a dbTree from the log. */ public DbTree() throws DatabaseException { this.envImpl = null; idDatabase = new DatabaseImpl(); idDatabase.setDebugDatabaseName(ID_DB_NAME); nameDatabase = new DatabaseImpl(); nameDatabase.setDebugDatabaseName(NAME_DB_NAME); } /** * Create a new dbTree for a new environment. */ public DbTree(EnvironmentImpl env) throws DatabaseException { this.envImpl = env; idDatabase = new DatabaseImpl(ID_DB_NAME, new DatabaseId(0), env, new DatabaseConfig()); nameDatabase = new DatabaseImpl(NAME_DB_NAME, new DatabaseId(1), env, new DatabaseConfig()); lastAllocatedDbId = 1; } /** * Get the latest allocated id, for checkpoint info. */ public synchronized int getLastDbId() { return lastAllocatedDbId; } /** * Get the next available database id. */ private synchronized int getNextDbId() { return ++lastAllocatedDbId; } /** * Initialize the db id, from recovery. */ public synchronized void setLastDbId(int maxDbId) { lastAllocatedDbId = maxDbId; } private Locker createLocker(EnvironmentImpl envImpl) throws DatabaseException { if (envImpl.isNoLocking()) { return new BasicLocker(envImpl); } else { return new AutoTxn(envImpl, new TransactionConfig()); } } /** * Set the db environment during recovery, after instantiating the tree from * the log. */ void setEnvironmentImpl(EnvironmentImpl envImpl) throws DatabaseException { this.envImpl = envImpl; idDatabase.setEnvironmentImpl(envImpl); nameDatabase.setEnvironmentImpl(envImpl); } /** * Create a database. */ public synchronized DatabaseImpl createDb(Locker locker, String databaseName, DatabaseConfig dbConfig, Database databaseHandle) throws DatabaseException { return createDb(locker, databaseName, dbConfig, databaseHandle, true); } /** * Create a database. * * @param locker * owning locker * @param databaseName * identifier for database * @param dbConfig * @param allowEviction * is whether eviction is allowed during cursor operations. */ public synchronized DatabaseImpl createDb(Locker locker, String databaseName, DatabaseConfig dbConfig, Database databaseHandle, boolean allowEviction) throws DatabaseException { /* Create a new database object. */ DatabaseId newId = new DatabaseId(getNextDbId()); DatabaseImpl newDb = new DatabaseImpl(databaseName, newId, envImpl, dbConfig); CursorImpl idCursor = null; CursorImpl nameCursor = null; boolean operationOk = false; Locker autoTxn = null; try { /* Insert it into name -> id db. */ nameCursor = new CursorImpl(nameDatabase, locker); nameCursor.setAllowEviction(allowEviction); LN nameLN = new NameLN(newId); nameCursor.putLN(databaseName.getBytes("UTF-8"), nameLN, false); /* * If this is a non-handle use, no need to record any handle locks. */ if (databaseHandle != null) { locker.addToHandleMaps(new Long(nameLN.getNodeId()), databaseHandle); } /* Insert it into id -> name db, in auto commit mode. */ autoTxn = createLocker(envImpl); idCursor = new CursorImpl(idDatabase, autoTxn); idCursor.setAllowEviction(allowEviction); idCursor.putLN(newId.getBytes(), new MapLN(newDb), false); operationOk = true; } catch (UnsupportedEncodingException UEE) { throw new DatabaseException(UEE); } finally { if (idCursor != null) { idCursor.close(); } if (nameCursor != null) { nameCursor.close(); } if (autoTxn != null) { autoTxn.operationEnd(operationOk); } } return newDb; } /** * Called by the Tree to propagate a root change. If the tree is a data * database, we will write the MapLn that represents this db to the log. If * the tree is one of the mapping dbs, we'll write the dbtree to the log. * * @param db * the target db */ public void modifyDbRoot(DatabaseImpl db) throws DatabaseException { if (db.getId().equals(ID_DB_ID) || db.getId().equals(NAME_DB_ID)) { envImpl.logMapTreeRoot(); } else { Locker locker = createLocker(envImpl); CursorImpl cursor = new CursorImpl(idDatabase, locker); boolean operationOk = false; try { DatabaseEntry keyDbt = new DatabaseEntry(db.getId().getBytes()); MapLN mapLN = null; /* * Retry indefinitely in the face of lock timeouts since the * lock on the MapLN is only supposed to be held for short * periods. */ while (true) { try { boolean searchOk = (cursor.searchAndPosition(keyDbt, new DatabaseEntry(), SearchMode.SET, LockType.WRITE) & CursorImpl.FOUND) != 0; if (!searchOk) { throw new DatabaseException("can't find database " + db.getId()); } mapLN = (MapLN) cursor .getCurrentLNAlreadyLatched(LockType.WRITE); assert mapLN != null; /* Should be locked. */ } catch (DeadlockException DE) { cursor.close(); locker.operationEnd(false); locker = createLocker(envImpl); cursor = new CursorImpl(idDatabase, locker); continue; } finally { cursor.releaseBINs(); } break; } RewriteMapLN writeMapLN = new RewriteMapLN(cursor); mapLN.getDatabase().getTree().withRootLatchedExclusive( writeMapLN); operationOk = true; } finally { if (cursor != null) { cursor.close(); } locker.operationEnd(operationOk); } } } private static class RewriteMapLN implements WithRootLatched { private CursorImpl cursor; RewriteMapLN(CursorImpl cursor) { this.cursor = cursor; } public IN doWork(ChildReference root) throws DatabaseException { DatabaseEntry dataDbt = new DatabaseEntry(new byte[0]); cursor.putCurrent(dataDbt, null, null); return null; } } /* * Helper for database operations. This method positions a cursor on the * NameLN that represents this database and write locks it. */ private NameLockResult lockNameLN(Locker locker, String databaseName, String action) throws DatabaseException { /* * We have to return both a cursor on the nameing tree and a reference * to the found DatabaseImpl. */ NameLockResult result = new NameLockResult(); /* Find the existing DatabaseImpl and establish a cursor. */ result.dbImpl = getDb(locker, databaseName, null); if (result.dbImpl == null) { throw new DatabaseNotFoundException("Attempted to " + action + " non-existent database " + databaseName); } result.nameCursor = new CursorImpl(nameDatabase, locker); try { /* Position the cursor at the specified NameLN. */ DatabaseEntry key = new DatabaseEntry(databaseName .getBytes("UTF-8")); boolean found = (result.nameCursor.searchAndPosition(key, null, SearchMode.SET, LockType.WRITE) & CursorImpl.FOUND) != 0; if (!found) { result.nameCursor.releaseBIN(); result.nameCursor.close(); result.nameCursor = null; return result; } /* Call getCurrentLN to write lock the nameLN. */ result.nameLN = (NameLN) result.nameCursor .getCurrentLNAlreadyLatched(LockType.WRITE); assert result.nameLN != null; /* Should be locked. */ /* * Check the open handle count after we have the write lock and no * other transactions can open. XXX, another handle using the same * txn could open ... */ int handleCount = result.dbImpl.getReferringHandleCount(); if (handleCount > 0) { throw new DatabaseException("Can't " + action + " database " + databaseName + "," + handleCount + " open Dbs exist"); } } catch (UnsupportedEncodingException UEE) { result.nameCursor.releaseBIN(); result.nameCursor.close(); throw new DatabaseException(UEE); } catch (DatabaseException e) { result.nameCursor.releaseBIN(); result.nameCursor.close(); throw e; } return result; } private static class NameLockResult { CursorImpl nameCursor; DatabaseImpl dbImpl; NameLN nameLN; } /** * Return true if the operation succeeded, false otherwise. */ boolean dbRename(Locker locker, String databaseName, String newName) throws DatabaseException { CursorImpl nameCursor = null; try { NameLockResult result = lockNameLN(locker, databaseName, "rename"); nameCursor = result.nameCursor; if (nameCursor == null) { return false; } else { /* * Rename simply deletes the one entry in the naming tree and * replaces it with a new one. Remove the oldName->dbId entry * and insert newName->dbId. */ nameCursor.delete(); nameCursor.putLN(newName.getBytes("UTF-8"), new NameLN( result.dbImpl.getId()), false); result.dbImpl.setDebugDatabaseName(newName); return true; } } catch (UnsupportedEncodingException UEE) { throw new DatabaseException(UEE); } finally { if (nameCursor != null) { nameCursor.releaseBIN(); nameCursor.close(); } } } /** * Remove the database by deleting the nameLN. */ void dbRemove(Locker locker, String databaseName) throws DatabaseException { CursorImpl nameCursor = null; try { NameLockResult result = lockNameLN(locker, databaseName, "remove"); nameCursor = result.nameCursor; if (nameCursor == null) { return; } else { /* * Delete the NameLN. There's no need to mark any Database * handle invalid, because the handle must be closed when we * take action and any further use of the handle will re-look up * the database. */ nameCursor.delete(); /* * Schedule database for final deletion during commit. This * should be the last action taken, since this will take effect * immediately for non-txnal lockers. */ locker.markDeleteAtTxnEnd(result.dbImpl, true); } } finally { if (nameCursor != null) { nameCursor.releaseBIN(); nameCursor.close(); } } } /** * To truncate, remove the database named by databaseName and create a new * database in its place. * * @param returnCount * if true, must return the count of records in the database, * which can be an expensive option. */ long truncate(Locker locker, String databaseName, boolean returnCount) throws DatabaseException { CursorImpl nameCursor = null; Locker autoTxn = null; try { NameLockResult result = lockNameLN(locker, databaseName, "truncate"); nameCursor = result.nameCursor; if (nameCursor == null) { return 0; } else { /* * Make a new database with an empty tree. Make the nameLN refer * to the id of the new database. */ DatabaseId newId = new DatabaseId(getNextDbId()); DatabaseImpl newDb = (DatabaseImpl) result.dbImpl.clone(); newDb.setId(newId); newDb.setTree(new Tree(newDb)); /* * Insert the new MapLN into the id tree. Always use an AutoTxn * on the id databaase, because we can not hold long term locks * on the mapLN. */ CursorImpl idCursor = null; boolean operationOk = false; try { autoTxn = createLocker(envImpl); idCursor = new CursorImpl(idDatabase, autoTxn); idCursor.putLN(newId.getBytes(), new MapLN(newDb), false); operationOk = true; } finally { if (idCursor != null) { idCursor.close(); } if (autoTxn != null) { autoTxn.operationEnd(operationOk); } } result.nameLN.setId(newDb.getId()); /* If required, count the number of records in the database. */ long recordCount = 0; if (returnCount) { recordCount = result.dbImpl.countRecords(); } /* log the nameLN. */ DatabaseEntry dataDbt = new DatabaseEntry(new byte[0]); nameCursor.putCurrent(dataDbt, null, null); /* * Marking the lockers should be the last action, since it takes * effect immediately for non-txnal lockers. */ /* Schedule old database for deletion if txn commits. */ locker.markDeleteAtTxnEnd(result.dbImpl, true); /* Schedule new database for deletion if txn aborts. */ locker.markDeleteAtTxnEnd(newDb, false); return recordCount; } } catch (CloneNotSupportedException CNSE) { throw new DatabaseException(CNSE); } finally { if (nameCursor != null) { nameCursor.releaseBIN(); nameCursor.close(); } } } /* * Remove the mapLN that refers to this database. */ void deleteMapLN(DatabaseId id) throws DatabaseException { Locker autoTxn = null; boolean operationOk = false; CursorImpl idCursor = null; try { autoTxn = createLocker(envImpl); idCursor = new CursorImpl(idDatabase, autoTxn); boolean found = (idCursor.searchAndPosition(new DatabaseEntry(id .getBytes()), null, SearchMode.SET, LockType.WRITE) & CursorImpl.FOUND) != 0; if (found) { idCursor.delete(); } operationOk = true; } finally { if (idCursor != null) { idCursor.close(); } if (autoTxn != null) { autoTxn.operationEnd(operationOk); } } } /** * Truncate a database named by databaseName. Return the new DatabaseImpl * object that represents the truncated database. The old one is marked as * deleted. * * @deprecated This method used by Database.truncate() */ TruncateResult truncate(Locker locker, DatabaseImpl oldDatabase, boolean returnCount) throws DatabaseException { CursorImpl nameCursor = new CursorImpl(nameDatabase, locker); try { String databaseName = getDbName(oldDatabase.getId()); DatabaseEntry keyDbt = new DatabaseEntry(databaseName .getBytes("UTF-8")); boolean found = (nameCursor.searchAndPosition(keyDbt, null, SearchMode.SET, LockType.WRITE) & CursorImpl.FOUND) != 0; if (!found) { /* * Should be found, since truncate is instigated from * Database.truncate(); */ throw new DatabaseException("Database " + databaseName + " not found in map tree"); } /* Call getCurrentLN to write lock the nameLN. */ NameLN nameLN = (NameLN) nameCursor .getCurrentLNAlreadyLatched(LockType.WRITE); assert nameLN != null; /* Should be locked. */ /* * Check the open handle count after we have the write lock and no * other transactions can open. XXX, another handle using the same * txn could open ... */ int handleCount = oldDatabase.getReferringHandleCount(); if (handleCount > 1) { throw new DatabaseException("Can't truncate database " + databaseName + "," + handleCount + " open databases exist"); } /* * Make a new database with an empty tree. Make the nameLN refer to * the id of the new database. */ DatabaseImpl newDb; DatabaseId newId = new DatabaseId(getNextDbId()); newDb = (DatabaseImpl) oldDatabase.clone(); newDb.setId(newId); newDb.setTree(new Tree(newDb)); /* Insert the new db into id -> name map */ CursorImpl idCursor = null; boolean operationOk = false; Locker autoTxn = null; try { autoTxn = createLocker(envImpl); idCursor = new CursorImpl(idDatabase, autoTxn); idCursor.putLN(newId.getBytes(), new MapLN(newDb), false); operationOk = true; } finally { if (idCursor != null) { idCursor.close(); } if (autoTxn != null) { autoTxn.operationEnd(operationOk); } } nameLN.setId(newDb.getId()); /* count records for the deleted database. */ long count = 0; if (returnCount) { count = oldDatabase.countRecords(); } /* Schedule database for final deletion during commit. */ locker.markDeleteAtTxnEnd(oldDatabase, true); /* log the nameLN. */ DatabaseEntry dataDbt = new DatabaseEntry(new byte[0]); nameCursor.putCurrent(dataDbt, null, null); return new TruncateResult(newDb, (int) count); } catch (CloneNotSupportedException CNSE) { throw new DatabaseException(CNSE); } catch (UnsupportedEncodingException UEE) { throw new DatabaseException(UEE); } finally { nameCursor.releaseBIN(); nameCursor.close(); } } /** * Get a database object given a database name. */ public DatabaseImpl getDb(Locker nameLocker, String databaseName, Database databaseHandle) throws DatabaseException { return getDb(nameLocker, databaseName, databaseHandle, true); } /** * Get a database object given a database name. * * @param nameLocker * is used to access the NameLN. As always, a NullTxn is used to * access the MapLN. * @param databaseName * target database * @return null if database doesn't exist * @param allowEviction * is whether eviction is allowed during cursor operations. */ public DatabaseImpl getDb(Locker nameLocker, String databaseName, Database databaseHandle, boolean allowEviction) throws DatabaseException { try { /* * Search the nameDatabase tree for the NameLn for this name. * Release locks before searching the id tree */ CursorImpl nameCursor = null; DatabaseId id = null; try { nameCursor = new CursorImpl(nameDatabase, nameLocker); nameCursor.setAllowEviction(allowEviction); DatabaseEntry keyDbt = new DatabaseEntry(databaseName .getBytes("UTF-8")); boolean found = (nameCursor.searchAndPosition(keyDbt, null, SearchMode.SET, LockType.READ) & CursorImpl.FOUND) != 0; if (found) { NameLN nameLN = (NameLN) nameCursor .getCurrentLNAlreadyLatched(LockType.READ); assert nameLN != null; /* Should be locked. */ id = nameLN.getId(); /* * If this is a non-handle use, no need to record any handle * locks. */ if (databaseHandle != null) { nameLocker.addToHandleMaps( new Long(nameLN.getNodeId()), databaseHandle); } } } finally { if (nameCursor != null) { nameCursor.releaseBIN(); nameCursor.close(); } } /* * Now search the id tree. */ if (id == null) { return null; } else { return getDb(id, -1, allowEviction, databaseName); } } catch (UnsupportedEncodingException UEE) { throw new DatabaseException(UEE); } } /** * Get a database object based on an id only. Used by recovery, cleaning and * other clients who have an id in hand, and don't have a resident node, to * find the matching database for a given log entry. */ public DatabaseImpl getDb(DatabaseId dbId) throws DatabaseException { return getDb(dbId, -1); } /** * Get a database object based on an id only. Specify the lock timeout to * use, or -1 to use the default timeout. A timeout should normally only be * specified by daemons with their own timeout configuration. public for * unit tests. */ public DatabaseImpl getDb(DatabaseId dbId, long lockTimeout) throws DatabaseException { return getDb(dbId, lockTimeout, true, null); } /** * Get a database object based on an id only, caching the id-db mapping in * the given map. */ public DatabaseImpl getDb(DatabaseId dbId, long lockTimeout, Map dbCache) throws DatabaseException { if (dbCache.containsKey(dbId)) { return (DatabaseImpl) dbCache.get(dbId); } else { DatabaseImpl db = getDb(dbId, lockTimeout, true, null); dbCache.put(dbId, db); return db; } } /** * Get a database object based on an id only. Specify the lock timeout to * use, or -1 to use the default timeout. A timeout should normally only be * specified by daemons with their own timeout configuration. public for * unit tests. * * @param allowEviction * is whether eviction is allowed during cursor operations. */ public DatabaseImpl getDb(DatabaseId dbId, long lockTimeout, boolean allowEviction, String dbNameIfAvailable) throws DatabaseException { if (dbId.equals(idDatabase.getId())) { /* We're looking for the id database itself. */ return idDatabase; } else if (dbId.equals(nameDatabase.getId())) { /* We're looking for the name database itself. */ return nameDatabase; } else { Locker locker = new BasicLocker(envImpl); if (lockTimeout != -1) { locker.setLockTimeout(lockTimeout); } /* Scan the tree for this db. */ CursorImpl idCursor = null; DatabaseImpl foundDbImpl = null; /* * Retry in the face of lock timeouts. Deadlocks may be due to * conflicts with modifyDbRoot. */ while (true) { idCursor = new CursorImpl(idDatabase, locker); idCursor.setAllowEviction(allowEviction); try { DatabaseEntry keyDbt = new DatabaseEntry(dbId.getBytes()); boolean found = (idCursor.searchAndPosition(keyDbt, new DatabaseEntry(), SearchMode.SET, LockType.READ) & CursorImpl.FOUND) != 0; if (found) { MapLN mapLN = (MapLN) idCursor .getCurrentLNAlreadyLatched(LockType.READ); assert mapLN != null; /* Should be locked. */ foundDbImpl = mapLN.getDatabase(); } break; } catch (DeadlockException DE) { idCursor.close(); locker.operationEnd(false); locker = new BasicLocker(envImpl); if (lockTimeout != -1) { locker.setLockTimeout(lockTimeout); } idCursor = new CursorImpl(idDatabase, locker); idCursor.setAllowEviction(allowEviction); continue; } finally { idCursor.releaseBIN(); idCursor.close(); locker.operationEnd(true); } } /* * Set the debugging name in the databaseImpl, but only after * recovery had finished setting up the tree. */ if (envImpl.isOpen()) { setDebugNameForDatabaseImpl(foundDbImpl, dbNameIfAvailable); } return foundDbImpl; } } /* * We need to cache a database name in the dbImpl for later use in error * messages, when it may be unsafe to walk the mapping tree. Finding a name * by id is slow, so minimize the number of times we must set the debug * name. The debug name will only be uninitialized when an existing * databaseImpl is faulted in. */ private void setDebugNameForDatabaseImpl(DatabaseImpl dbImpl, String dbName) throws DatabaseException { if (dbImpl != null) { if (dbName != null) { /* If a name was provided, use that. */ dbImpl.setDebugDatabaseName(dbName); } else if (dbImpl.getDebugName() == null) { /* * Only worry about searching for a name if the name is * uninitialized. */ dbImpl.setDebugDatabaseName(getDbName(dbImpl.getId())); } } } /** * Rebuild the IN list after recovery. */ public void rebuildINListMapDb() throws DatabaseException { idDatabase.getTree().rebuildINList(); } /* * Verification, must be run while system is quiescent. */ public boolean verify(VerifyConfig config, PrintStream out) throws DatabaseException { boolean ret = true; try { /* For now, verify all databases. */ boolean ok = idDatabase.verify(config, idDatabase.getEmptyStats()); if (!ok) { ret = false; } ok = nameDatabase.verify(config, nameDatabase.getEmptyStats()); if (!ok) { ret = false; } } catch (DatabaseException DE) { ret = false; } synchronized (envImpl.getINCompressor()) { /* * Get a cursor on the id tree. Use objects at the dbi layer rather * than at the public api, in order to retrieve objects rather than * Dbts. Note that we don't do cursor cloning here, so any failures * from each db verify invalidate the cursor. Use dirty read * (LockMode.NONE) because locks on the MapLN should never be held * for long, as that will cause deadlocks with splits and * checkpointing. */ Locker locker = null; CursorImpl cursor = null; LockType lockType = LockType.NONE; try { locker = new BasicLocker(envImpl); cursor = new CursorImpl(idDatabase, locker); if (cursor.positionFirstOrLast(true, null)) { MapLN mapLN = (MapLN) cursor .getCurrentLNAlreadyLatched(lockType); DatabaseEntry keyDbt = new DatabaseEntry(); DatabaseEntry dataDbt = new DatabaseEntry(); while (true) { if (mapLN != null && !mapLN.isDeleted()) { DatabaseImpl dbImpl = mapLN.getDatabase(); boolean ok = dbImpl.verify(config, dbImpl .getEmptyStats()); if (!ok) { ret = false; } } /* Go on to the next entry. */ OperationStatus status = cursor.getNext(keyDbt, dataDbt, lockType, true, // go forward false); // do need to latch if (status != OperationStatus.SUCCESS) { break; } mapLN = (MapLN) cursor.getCurrentLN(lockType); } } } catch (DatabaseException e) { e.printStackTrace(out); ret = false; } finally { if (cursor != null) { cursor.releaseBINs(); cursor.close(); } if (locker != null) { locker.operationEnd(); } } } return ret; } /** * Return the database name for a given db. Slow, must traverse. Used by * truncate and for debugging. */ public String getDbName(DatabaseId id) throws DatabaseException { if (id.equals(ID_DB_ID)) { return ID_DB_NAME; } else if (id.equals(NAME_DB_ID)) { return NAME_DB_NAME; } Locker locker = null; CursorImpl cursor = null; try { locker = new BasicLocker(envImpl); cursor = new CursorImpl(nameDatabase, locker); /* Use dirty reads (LockType.NONE). */ DatabaseEntry keyDbt = new DatabaseEntry(); DatabaseEntry dataDbt = new DatabaseEntry(); String name = null; if (cursor.positionFirstOrLast(true, null)) { /* Fill in the key DatabaseEntry */ OperationStatus status = cursor.getCurrentAlreadyLatched( keyDbt, dataDbt, LockType.NONE, true); do { if (status == OperationStatus.SUCCESS) { NameLN nameLN = (NameLN) cursor .getCurrentLN(LockType.NONE); if (nameLN != null && nameLN.getId().equals(id)) { name = new String(keyDbt.getData(), "UTF-8"); break; } } /* Go on to the next entry. */ status = cursor.getNext(keyDbt, dataDbt, LockType.NONE, true, // go forward false); // do need to latch } while (status == OperationStatus.SUCCESS); } return name; } catch (UnsupportedEncodingException UEE) { throw new DatabaseException(UEE); } finally { if (cursor != null) { cursor.releaseBINs(); cursor.close(); } if (locker != null) { locker.operationEnd(); } } } /** * @return a list of database names held in the environment, as strings. */ public List getDbNames() throws DatabaseException { List nameList = new ArrayList(); Locker locker = null; CursorImpl cursor = null; try { locker = new BasicLocker(envImpl); cursor = new CursorImpl(nameDatabase, locker); DatabaseEntry keyDbt = new DatabaseEntry(); DatabaseEntry dataDbt = new DatabaseEntry(); if (cursor.positionFirstOrLast(true, null)) { OperationStatus status = cursor.getCurrentAlreadyLatched( keyDbt, dataDbt, LockType.READ, true); do { if (status == OperationStatus.SUCCESS) { String name = new String(keyDbt.getData(), "UTF-8"); if (!isReservedDbName(name)) { nameList.add(name); } } /* Go on to the next entry. */ status = cursor.getNext(keyDbt, dataDbt, LockType.READ, true, // go forward false); // do need to latch } while (status == OperationStatus.SUCCESS); } return nameList; } catch (UnsupportedEncodingException UEE) { throw new DatabaseException(UEE); } finally { if (cursor != null) { cursor.close(); } if (locker != null) { locker.operationEnd(); } } } /** * Returns true if the name is a reserved JE database name. */ public boolean isReservedDbName(String name) { for (int i = 0; i < RESERVED_DB_NAMES.length; i += 1) { if (RESERVED_DB_NAMES[i].equals(name)) { return true; } } return false; } /** * @return the higest level node in the environment. */ public int getHighestLevel() throws DatabaseException { /* The highest level in the map side */ RootLevel getLevel = new RootLevel(idDatabase); idDatabase.getTree().withRootLatchedShared(getLevel); int idHighLevel = getLevel.getRootLevel(); /* The highest level in the name side */ getLevel = new RootLevel(nameDatabase); nameDatabase.getTree().withRootLatchedShared(getLevel); int nameHighLevel = getLevel.getRootLevel(); return (nameHighLevel > idHighLevel) ? nameHighLevel : idHighLevel; } /* * RootLevel lets us write out the root IN within the root latch. */ private static class RootLevel implements WithRootLatched { private DatabaseImpl db; private int rootLevel; RootLevel(DatabaseImpl db) { this.db = db; rootLevel = 0; } public IN doWork(ChildReference root) throws DatabaseException { IN rootIN = (IN) root.fetchTarget(db, null); rootLevel = rootIN.getLevel(); return null; } int getRootLevel() { return rootLevel; } } /* * LoggableObject */ /** * @see LoggableObject#getLogType */ public LogEntryType getLogType() { return LogEntryType.LOG_ROOT; } /** * @see LoggableObject#marshallOutsideWriteLatch Can be marshalled outside * the log write latch. */ public boolean marshallOutsideWriteLatch() { return true; } /** * @see LoggableObject#countAsObsoleteWhenLogged */ public boolean countAsObsoleteWhenLogged() { return false; } /** * @see LoggableObject#getLogSize */ public int getLogSize() { return LogUtils.getIntLogSize() + // last allocated id idDatabase.getLogSize() + // id db nameDatabase.getLogSize(); // name db } /** * @see LoggableObject#writeToLog */ public void writeToLog(ByteBuffer logBuffer) { LogUtils.writeInt(logBuffer, lastAllocatedDbId); // last id idDatabase.writeToLog(logBuffer); // id db nameDatabase.writeToLog(logBuffer); // name db } /** * @see LoggableObject#postLogWork */ public void postLogWork(long justLoggedLsn) throws DatabaseException { } /* * LogReadable */ /** * @see LogReadable#readFromLog */ public void readFromLog(ByteBuffer itemBuffer, byte entryTypeVersion) throws LogException { lastAllocatedDbId = LogUtils.readInt(itemBuffer); // last id idDatabase.readFromLog(itemBuffer, entryTypeVersion); // id db nameDatabase.readFromLog(itemBuffer, entryTypeVersion); // name db } /** * @see LogReadable#dumpLog */ public void dumpLog(StringBuffer sb, boolean verbose) { sb.append("<dbtree lastId = \""); sb.append(lastAllocatedDbId); sb.append("\">"); sb.append("<idDb>"); idDatabase.dumpLog(sb, verbose); sb.append("</idDb><nameDb>"); nameDatabase.dumpLog(sb, verbose); sb.append("</nameDb>"); sb.append("</dbtree>"); } /** * @see LogReadable#logEntryIsTransactional. */ public boolean logEntryIsTransactional() { return false; } /** * @see LogReadable#getTransactionId */ public long getTransactionId() { return 0; } /* * For unit test support */ String dumpString(int nSpaces) { StringBuffer self = new StringBuffer(); self.append(TreeUtils.indent(nSpaces)); self.append("<dbTree lastDbId =\""); self.append(lastAllocatedDbId); self.append("\">"); self.append('\n'); self.append(idDatabase.dumpString(nSpaces + 1)); self.append('\n'); self.append(nameDatabase.dumpString(nSpaces + 1)); self.append('\n'); self.append("</dbtree>"); return self.toString(); } public String toString() { return dumpString(0); } /** * For debugging. */ public void dump() throws DatabaseException { idDatabase.getTree().dump(); nameDatabase.getTree().dump(); } }